import {
    db, UserModel, SettingModel, Handler, NotFoundError, param, PRIV, Types, paginate, query
} from 'hydrooj';

import { deleteUserCache } from 'hydrooj/src/model/user';

const collbd = db.collection('badge');
const collubd = db.collection('user.badge');
const collusr = db.collection('user');

interface UserBadge {
    _id: ObjectId;
    owner: number;
    badgeId: number;
    getAt: Date;
}

interface Badge {
    _id: number;
    short: string;
    title: string;
    backgroundColor: string;
    fontColor: string;
    content: string;
    users: [number];
    createAt: Date;
}

declare module 'hydrooj' {
    interface Model {
        userBadge: typeof UserBadgeModel;
        badge: typeof BadgeModel;
    }
    interface Collections {
        userBadge: UserBadge;
        badge: Badge;
    }
}

const UserBadgeModel = { userBadgeAdd, userBadgeGetMulti, userBadgeDel, userBadgeSel };
const BadgeModel = { BadgeGetMulti, BadgeAdd, BadgeGet, BadgeEdit, BadgeDel };
global.Hydro.model.userBadge = UserBadgeModel;
global.Hydro.model.badge = BadgeModel;

async function setUserBadge(userId: number, badgeId: number, badge: String): Promise<number> {
    const result = await UserModel.setById(userId, { badgeId: badgeId, badge: badge });
    return result;
}

async function resetBadge(badgeId: number, badge: String): Promise<number> {
    const result = (await collusr.updateMany({ badgeId: badgeId }, { $set: { badge: badge } })).modifiedCount;
    if (result) {
        await deleteUserCache(true);
    }
    return result;
}

async function unsetUserBadge(userId: number): Promise<number> {
    const result = await UserModel.setById(userId, { badgeId: '', badge: '' });
    return result._id;
}

async function unsetBadge(badgeId: number): Promise<number> {
    const result = (await collusr.updateMany({ badgeId: badgeId }, { $unset: { badgeId: '', badge: '' } })).modifiedCount;
    if (result) {
        await deleteUserCache(true);
    }
    return result;
}

async function userBadgeAdd(userId: number, badgeId: number): Promise<string> {
    const result = await collubd.insertOne({
        owner: userId,
        badgeId: badgeId,
        getAt: new Date()
    })
    return result.insertedId;
}

async function userBadgeGetMulti(userId: number): Promise<UserBadge[]> {
    return await collubd.find({ owner: userId }).sort({ badgeId: 1 });
}


async function userBadgeDel(userId: number, badgeId: number): Promise<number> {
    if ((await collusr.findOne({ _id: userId })).badgeId === badgeId) {
        await unsetUserBadge(userId);
    }
    return (await collubd.deleteOne({ owner: userId, badgeId: badgeId })).deletedCount;
}

async function userBadgeSel(userId: number, badgeId: number): Promise<number> {
    const userBadgeId = await collubd.findOne({ owner: userId, badgeId: badgeId });
    if (userBadgeId) {
        const badge: Badge = await collbd.findOne({ _id: badgeId });
        const badgeid: number = badge._id;
        const payload: string = badge.short + badge.backgroundColor + badge.fontColor + '#' + badge.title;
        return await setUserBadge(userId, badgeid, payload);
    } else {
        return 0;
    }
}

async function BadgeGetMulti(): Promise<Badge[]> {
    return await collbd.find({});
}

async function BadgeAdd(short: string, title: string, backgroundColor: string, fontColor: string, content: string, users: [number], badgeId?: number,): Promise<number> {
    if (typeof badgeId !== 'number') {
        const [badge] = await collbd.find({}).sort({ _id: -1 }).limit(1).toArray();
        badgeId = Math.max((badge?._id || 0) + 1, 1);
    };
    const result = await collbd.insertOne({
        _id: badgeId,
        short: short,
        title: title,
        backgroundColor: backgroundColor,
        fontColor: fontColor,
        content: content,
        users: users,
        createAt: new Date()
    });
    if (users) {
        for (const userId of users) {
            await UserBadgeModel.userBadgeAdd(userId, badgeId);
        }
    }
    return result.insertedId;
}

async function BadgeGet(badgeId: number): Promise<Badge> {
    return await collbd.findOne({ _id: badgeId });
}

async function BadgeEdit(badgeId: number, short: string, title: string, backgroundColor: string, fontColor: string, content: string, users: [number], users_old: [number]): Promise<number> {
    const result = await collbd.updateOne({ _id: badgeId }, {
        $set: {
            short: short,
            title: title,
            backgroundColor: backgroundColor,
            fontColor: fontColor,
            content: content,
            users: users
        }
    });
    if (users_old) {
        for (const userId of users_old) {
            if (!users || !users.includes(userId))
                await UserBadgeModel.userBadgeDel(userId, badgeId);
        }
    }
    if (users) {
        for (const userId of users) {
            if (!users_old || !users_old.includes(userId))
                await UserBadgeModel.userBadgeAdd(userId, badgeId);
        }
    }
    const badge: string = short + backgroundColor + fontColor + '#' + title;
    await resetBadge(badgeId, badge);
    return result.modifiedCount;
}

async function BadgeDel(badgeId: number): Promise<number> {
    const result = await collbd.deleteOne({ _id: badgeId });
    await collubd.deleteMany({ badgeId: badgeId });
    await unsetBadge(badgeId);
    return result.deletedCount;
}

class UserBadgeManageHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1, uid = this.user._id) {
        const [ddocs, dpcount] = await this.paginate(
            await UserBadgeModel.userBadgeGetMulti(uid),
            page,
            20
        );
        for (const ddoc of ddocs) {
            ddoc.badge = await BadgeModel.BadgeGet(ddoc.badgeId);
        }
        const udoc = await UserModel.getById(domainId, uid);
        this.response.template = 'badge_mybadge.html';
        this.response.body = { ddocs, dpcount, page, udoc }
    }

    @param('badgeId', Types.PositiveInt)
    async postEnable(domainId: string, badgeId: number) {
        await userBadgeSel(this.user._id, badgeId);
        this.response.redirect = this.url('badge_mybadge');
    }

    async postReset(domainId: string) {
        await unsetUserBadge(this.user._id);
        this.response.redirect = this.url('badge_mybadge');
    }
}

class BadgeManageHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1) {
        const [ddocs, dpcount] = await this.paginate(
            await BadgeModel.BadgeGetMulti(),
            page,
            20
        );
        this.response.template = 'badge_manage.html';
        this.response.body = { ddocs, dpcount, page };
    }
}

class BadgeAddHandler extends Handler {
    async get(domainId: string) {
        this.response.template = 'badge_add.html';
    }

    @param('short', Types.String)
    @param('title', Types.String)
    @param('backgroundColor', Types.String)
    @param('fontColor', Types.String)
    @param('content', Types.Content)
    @param('users', Types.NumericArray, true)
    async post(domainId: string, short: string, title: string, backgroundColor: string, fontColor: string, content: string, users: [number]) {
      const badgeId = await BadgeAdd(short, title, backgroundColor, fontColor, content, users);
      this.response.redirect = this.url('badge_detail', { id: badgeId });
    }
}

class BadgeEditHandler extends Handler {
    @param('id', Types.PositiveInt)
    async get(domainId: string, id: number) {
        const badge = await BadgeModel.BadgeGet(id);
        if (!badge) throw new NotFoundError(`徽章 ${id} 不存在！`);
        this.response.template = 'badge_edit.html';
        this.response.body = { badge }
    }

    @param('id', Types.PositiveInt)
    @param('short', Types.String)
    @param('title', Types.String)
    @param('backgroundColor', Types.String)
    @param('fontColor', Types.String)
    @param('content', Types.Content)
    @param('users', Types.NumericArray, true)
    async postUpdate(domainId: string, id: number, short: string, title: string, backgroundColor: string, fontColor: string, content: string, users: [number]) {
        const users_old = (await BadgeModel.BadgeGet(id)).users;
        await BadgeEdit(id, short, title, backgroundColor, fontColor, content, users, users_old);
        this.response.redirect = this.url('badge_detail', { id });
    }

    @param('id', Types.PositiveInt)
    async postDelete(domainId: string, id: number) {
      await BadgeModel.BadgeDel(id);
      this.response.redirect = this.url('badge_manage');
    }
}

class BadgeDetailHandler extends Handler {
    @param('id', Types.PositiveInt, true)
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, id: number, page = 1) {
        const badge = await BadgeModel.BadgeGet(id);
        if (!badge) throw new NotFoundError(`徽章 ${id} 不存在！`);
        const userIds = badge.users?.sort((a, b) => a - b) || [];
        const [dudocs, upcount] = await this.paginate(
            UserModel.getMulti({ _id: { $in: userIds } }),
            page,
            20
        );

        const udict = await UserModel.getList(domainId, dudocs.map((x) => x._id));
        const udocs = dudocs.map((x) => udict[x._id]);

        this.response.template = 'badge_detail.html';
        this.response.body = { badge, udocs, upcount, page };
    }
}

class BadgeShowHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1) {
        const [dudocs, upcount] = await this.paginate(
            UserModel.getMulti({ badge: { $exists: true, $ne: "" } }),
            page,
            'ranking'
        );
        const udict = await UserModel.getList(domainId, dudocs.map((x) => x._id));
        const udocs = dudocs.map((x) => udict[x._id]);
        this.response.template = 'badge_show.html'; // 返回此页面
        this.response.body = { udocs, upcount, page };
    }
}

export async function apply(ctx: Context) {
    ctx.inject(['setting'], (c) => {
        c.setting.AccountSetting(
            SettingModel.Setting('setting_storage', 'badgeId', 0, 'number', 'badgeId', null, 3)
        );
    });

    ctx.on('handler/after/UserDetail#get', async (h) => {
        const uid = h.response.body.udoc?._id;
        if (!uid) {
            h.response.body.udoc.badges = [];
            return;
        }
        try {
            const cursor = await UserBadgeModel.userBadgeGetMulti(uid);
            const ddocs = await cursor.toArray();
            for (const ddoc of ddocs) {
                ddoc.badge = await BadgeModel.BadgeGet(ddoc.badgeId);
            }
            h.response.body.badges = ddocs;
        } catch (error) {
            h.response.body.badges = [];
        }
    });
    ctx.Route('badge_show', '/badge/show', BadgeShowHandler);
    ctx.Route('badge_manage', '/badge/manage', BadgeManageHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('badge_add', '/badge/add', BadgeAddHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('badge_mybadge', '/badge/mybadge', UserBadgeManageHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('badge_detail', '/badge/:id', BadgeDetailHandler);
    ctx.Route('badge_edit', '/badge/:id/edit', BadgeEditHandler, PRIV.PRIV_SET_PERM);
    ctx.injectUI('UserDropdown', 'badge_mybadge', (h) => ({ icon: 'crown', displayName: '我的徽章' }));
}
